#ifndef TRACE_H
#define TRACE_H
#include <stdio.h>
#include "cutil_math.h"
#include "objects.h"
#include "ppm.h"
#include "scene.h"

#define MAX_DIST 1e30
#define EPSILON  1e-3
#define ASCII_ESC 27
#define TERM_WIDTH 80

#define BLOCK_W 16 // BLOCK_W * BLOCK_H must be a multiple of 32
#define BLOCK_H 8
#define NUM_THREADS (BLOCK_W * BLOCK_H)

#define NONE       0
#define SPHERE     1
#define PLANE      2
#define TRIANGLE   3
#define M_TRIANGLE 4

#ifndef max
	#define max(a, b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef min
	#define min(a, b) (((a) < (b)) ? (a) : (b))
#endif

/* prototypes */
__global__ void  doCudaRaytrace(int);
__device__ void  cudaTrace(int);
__device__ void  getNearest(int);
__device__ void  filterBuffers(int);
__device__ void  shadeLight(int);
__device__ void  testTriangles(int, triangle *, int);
__device__ void  testSpheres(int, sphere *, int);
__device__ void  testPlanes(int, plane *, int);
__device__ void  testMeshes(int, mesh *, int);
__device__ float m_triangleTest(m_triangle, ray);
__device__ float triangleTest(triangle, ray);
__device__ float sphereTest(sphere, ray);
__device__ float planeTest(plane, ray);
__device__ float boxTest(box, ray);
__device__ float getRefract(int, float3);

__device__ __constant__ surface  sfs[500];
__device__ __constant__ color      *out;
__device__ __constant__ mesh       *ms;
__device__ __constant__ triangle   *ts;
__device__ __constant__ sphere     *ss;
__device__ __constant__ plane      *ps;
__device__ __constant__ light      *ls;
__device__ __constant__ float4 cam_mat[4];
__device__ __constant__ float  px_size;
__device__ __constant__ float  refract_idx;
__device__ __constant__ int max_bounces;
__device__ __constant__ int n_sfs;
__device__ __constant__ int n_ms;
__device__ __constant__ int n_ts;
__device__ __constant__ int n_ss;
__device__ __constant__ int n_ps;
__device__ __constant__ int n_ls;
__device__ __constant__ int aa2;
__device__ __constant__ int aa;
__device__ __constant__ int w;
__device__ __constant__ int h;


// temporaries for getting objects out of global memory
__shared__ triangle   tmp_t;
__shared__ sphere     tmp_s;
__shared__ plane      tmp_p;
__shared__ m_triangle tmp_mt;
__shared__ l_mesh     tmp_m;

// buffers for finding the nearest intersection
__shared__ float results_buffer[BLOCK_W * BLOCK_H];
__shared__ void *addrs_buffer[BLOCK_W * BLOCK_H];

// buffer of meshes, each meshs gets its own float
extern __shared__ float mesh_buffer[];

// type,  address, and mesh type of the nearest item
__shared__ int   type;
__shared__ void *object;


// x and y ar the pixel placement on the screen, idx is x + y * width
__shared__ int x, y, idx;

// used to remember the refractive index of the starting point
__shared__ float refract_start, shared_transparency;

// to remember where the camera starts
__shared__ float3 camera_start;

// used calculating lighting
__shared__ float3 view_dir;

// px_color is accumled output color, tmp_color current bounces color
__shared__ float3 px_color, tmp_color, shared_color_mult;


// used for calculating lighting and new direction
__shared__ float3 normal;

__shared__ int bounce, sf_id, lt_counter, miss;
__shared__ float reflectivity, distance, cur_refract, cv;
__shared__ ray r, to_l;

__device__
triangle getTriangle(float *start, size_t spacing)
{
	triangle t;
	t.point = make_float3(*(start),
			                  *(start + spacing),
		                    *(start + spacing * 2));
	t.v[0] = make_float3(*(start + spacing * 3),
			                 *(start + spacing * 4),
		                   *(start + spacing * 5));
	t.v[1] = make_float3(*(start + spacing * 6),
			                 *(start + spacing * 7),
		                   *(start + spacing * 8));
	t.normal = make_float3(*(start + spacing * 9),
			                   *(start + spacing * 10),
		                     *(start + spacing * 11));
	t.sf_id = *((int *)(start + spacing * 12));
	return t;
}

/*
__device__
triangle getTriangle(float *start, size_t spacing)
{
	triangle t;
	t.point = make_float3(*(start),
			                  *(start + spacing),
		                    *(start + spacing * 2));
	t.v[0] = make_float3(*(start + spacing * 3),
			                 *(start + spacing * 4),
		                   *(start + spacing * 5));
	t.v[1] = make_float3(*(start + spacing * 6),
			                 *(start + spacing * 7),
		                   *(start + spacing * 8));
	t.normal = make_float3(*(start + spacing * 9),
			                   *(start + spacing * 10),
		                     *(start + spacing * 11));
	t.sf_id = *((int *)(start + spacing * 12));
	return t;
}
*/

__device__
sphere getSphere(float * start, size_t spacing)
{
	sphere s;
	s.center = make_float3(*(start),
	                       *(start + spacing),
	                       *(start + spacing * 2));
	s.radius = *(start + spacing * 3);
	s.sf_id = *((int *)(start + spacing * 4));
	return s;
}

__device__
plane getPlane(float *start, size_t spacing)
{
	plane p;
	p.point = make_float3(*(start),
		                    *(start + spacing),
		                    *(start + spacing * 2));
	p.normal = make_float3(*(start + spacing * 3),
		                     *(start + spacing * 4),
		                     *(start + spacing * 5));
	p.sf_id = *((int *)(start + spacing * 6));
	return p;
}

__device__
box getBox(float *start, size_t spacing)
{
	box b;
	b.mins = make_float3(*start, *(start + spacing), *(start + 2 * spacing));
	b.maxs = make_float3(*(start + 3 * spacing),
	                     *(start + 4 * spacing),
	                     *(start + 5 * spacing));
	return b;
}


// fetch a mesh except for its box
__device__
l_mesh getLMesh(float *start, size_t spacing)
{
	l_mesh m;
	m.t    = *((m_triangle **)(start + spacing *((sizeof(box) / sizeof(size_t)) + 0)));
	m.n_ts = *((int *)(start + spacing * ((sizeof(box) / sizeof(size_t)) + 1)));
	return m;
}


__device__
m_triangle getMTriangle(float *start, size_t spacing)
{
	m_triangle t;
	t.point = make_float3(*(start),
			                  *(start + spacing * 1),
		                    *(start + spacing * 2));

	t.v[0] = make_float3(*(start + spacing * 3),
			                 *(start + spacing * 4),
		                   *(start + spacing * 5));
	t.v[1] = make_float3(*(start + spacing * 6),
		                   *(start + spacing * 7),
		                   *(start + spacing * 8));
	t.normal = make_float3(*(start + spacing * 9),
		                     *(start + spacing * 10),
		                     *(start + spacing * 11));
	t.normals[0] = make_float3(*(start + spacing * 12),
		                         *(start + spacing * 13),
		                         *(start + spacing * 14));
	t.normals[1] = make_float3(*(start + spacing * 15),
		                         *(start + spacing * 16),
		                         *(start + spacing * 17));
	t.normals[2] = make_float3(*(start + spacing * 18),
		                         *(start + spacing * 19),
		                         *(start + spacing * 20));
	t.sf_id = *((int *) (start + spacing * 21));
	return t;
}

#endif
